/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2002,2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xerces.internal.dom;

import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;


/**
 * DefaultNodeIterator implements a NodeIterator, which iterates a
 * DOM tree in the expected depth first way.
 *
 * <p>The whatToShow and filter functionality is implemented as expected.
 *
 * <p>This class also has method removeNode to enable iterator "fix-up"
 * on DOM remove. It is expected that the DOM implementation call removeNode
 * right before the actual DOM transformation. If not called by the DOM,
 * the client could call it before doing the removal.
 *
 * @xerces.internal
 */
public class NodeIteratorImpl implements NodeIterator {

  //
  // Data
  //

  /**
   * The DocumentImpl which created this iterator, so it can be detached.
   */
  private DocumentImpl fDocument;
  /**
   * The root.
   */
  private Node fRoot;
  /**
   * The whatToShow mask.
   */
  private int fWhatToShow = NodeFilter.SHOW_ALL;
  /**
   * The NodeFilter reference.
   */
  private NodeFilter fNodeFilter;
  /**
   * If detach is called, the fDetach flag is true, otherwise flase.
   */
  private boolean fDetach = false;

  //
  // Iterator state - current node and direction.
  //
  // Note: The current node and direction are sufficient to implement
  // the desired behaviour of the current pointer being _between_
  // two nodes. The fCurrentNode is actually the last node returned,
  // and the
  // direction is whether the pointer is in front or behind this node.
  // (usually akin to whether the node was returned via nextNode())
  // (eg fForward = true) or previousNode() (eg fForward = false).
  // Note also, if removing a Node, the fCurrentNode
  // can be placed on a Node which would not pass filters.

  /**
   * The last Node returned.
   */
  private Node fCurrentNode;

  /**
   * The direction of the iterator on the fCurrentNode.
   * <pre>
   *  nextNode()  ==      fForward = true;
   *  previousNode() ==   fForward = false;
   *  </pre>
   */
  private boolean fForward = true;

  /**
   * When TRUE, the children of entites references are returned in the iterator.
   */
  private boolean fEntityReferenceExpansion;

  //
  // Constructor
  //

  /**
   * Public constructor
   */
  public NodeIteratorImpl(DocumentImpl document,
      Node root,
      int whatToShow,
      NodeFilter nodeFilter,
      boolean entityReferenceExpansion) {
    fDocument = document;
    fRoot = root;
    fCurrentNode = null;
    fWhatToShow = whatToShow;
    fNodeFilter = nodeFilter;
    fEntityReferenceExpansion = entityReferenceExpansion;
  }

  public Node getRoot() {
    return fRoot;
  }

  // Implementation Note: Note that the iterator looks at whatToShow
  // and filter values at each call, and therefore one _could_ add
  // setters for these values and alter them while iterating!

  /**
   * Return the whatToShow value
   */
  public int getWhatToShow() {
    return fWhatToShow;
  }

  /**
   * Return the filter
   */
  public NodeFilter getFilter() {
    return fNodeFilter;
  }

  /**
   * Return whether children entity references are included in the iterator.
   */
  public boolean getExpandEntityReferences() {
    return fEntityReferenceExpansion;
  }

  /**
   * Return the next Node in the Iterator. The node is the next node in
   * depth-first order which also passes the filter, and whatToShow.
   * If there is no next node which passes these criteria, then return null.
   */
  public Node nextNode() {

    if (fDetach) {
      throw new DOMException(
          DOMException.INVALID_STATE_ERR,
          DOMMessageFormatter
              .formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
    }

    // if root is null there is no next node.
    if (fRoot == null) {
      return null;
    }

    Node nextNode = fCurrentNode;
    boolean accepted = false; // the next node has not been accepted.

    accepted_loop:
    while (!accepted) {

      // if last direction is not forward, repeat node.
      if (!fForward && nextNode != null) {
        //System.out.println("nextNode():!fForward:"+fCurrentNode.getNodeName());
        nextNode = fCurrentNode;
      } else {
        // else get the next node via depth-first
        if (!fEntityReferenceExpansion
            && nextNode != null
            && nextNode.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
          nextNode = nextNode(nextNode, false);
        } else {
          nextNode = nextNode(nextNode, true);
        }
      }

      fForward = true; //REVIST: should direction be set forward before null check?

      // nothing in the list. return null.
      if (nextNode == null) {
        return null;
      }

      // does node pass the filters and whatToShow?
      accepted = acceptNode(nextNode);
      if (accepted) {
        // if so, then the node is the current node.
        fCurrentNode = nextNode;
        return fCurrentNode;
      } else {
        continue accepted_loop;
      }

    } // while (!accepted) {

    // no nodes, or no accepted nodes.
    return null;

  }

  /**
   * Return the previous Node in the Iterator. The node is the next node in
   * _backwards_ depth-first order which also passes the filter, and whatToShow.
   */
  public Node previousNode() {

    if (fDetach) {
      throw new DOMException(
          DOMException.INVALID_STATE_ERR,
          DOMMessageFormatter
              .formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
    }

    // if the root is null, or the current node is null, return null.
    if (fRoot == null || fCurrentNode == null) {
      return null;
    }

    Node previousNode = fCurrentNode;
    boolean accepted = false;

    accepted_loop:
    while (!accepted) {

      if (fForward && previousNode != null) {
        //repeat last node.
        previousNode = fCurrentNode;
      } else {
        // get previous node in backwards depth first order.
        previousNode = previousNode(previousNode);
      }

      // we are going backwards
      fForward = false;

      // if the new previous node is null, we're at head or past the root,
      // so return null.
      if (previousNode == null) {
        return null;
      }

      // check if node passes filters and whatToShow.
      accepted = acceptNode(previousNode);
      if (accepted) {
        // if accepted, update the current node, and return it.
        fCurrentNode = previousNode;
        return fCurrentNode;
      } else {
        continue accepted_loop;
      }
    }
    // there are no nodes?
    return null;
  }

  /**
   * The node is accepted if it passes the whatToShow and the filter.
   */
  boolean acceptNode(Node node) {

    if (fNodeFilter == null) {
      return (fWhatToShow & (1 << node.getNodeType() - 1)) != 0;
    } else {
      return ((fWhatToShow & (1 << node.getNodeType() - 1)) != 0)
          && fNodeFilter.acceptNode(node) == NodeFilter.FILTER_ACCEPT;
    }
  }

  /**
   * Return node, if matches or any parent if matches.
   */
  Node matchNodeOrParent(Node node) {
    // Additions and removals in the underlying data structure may occur
    // before any iterations, and in this case the reference_node is null.
    if (fCurrentNode == null) {
      return null;
    }

    // check if the removed node is an _ancestor_ of the
    // reference node
    for (Node n = fCurrentNode; n != fRoot; n = n.getParentNode()) {
      if (node == n) {
        return n;
      }
    }
    return null;
  }

  /**
   * The method nextNode(Node, boolean) returns the next node
   * from the actual DOM tree.
   *
   * The boolean visitChildren determines whether to visit the children.
   * The result is the nextNode.
   */
  Node nextNode(Node node, boolean visitChildren) {

    if (node == null) {
      return fRoot;
    }

    Node result;
    // only check children if we visit children.
    if (visitChildren) {
      //if hasChildren, return 1st child.
      if (node.hasChildNodes()) {
        result = node.getFirstChild();
        return result;
      }
    }

    if (node == fRoot) { //if Root has no kids
      return null;
    }

    // if hasSibling, return sibling
    result = node.getNextSibling();
    if (result != null) {
      return result;
    }

    // return parent's 1st sibling.
    Node parent = node.getParentNode();
    while (parent != null && parent != fRoot) {
      result = parent.getNextSibling();
      if (result != null) {
        return result;
      } else {
        parent = parent.getParentNode();
      }

    } // while (parent != null && parent != fRoot) {

    // end of list, return null
    return null;
  }

  /**
   * The method previousNode(Node) returns the previous node
   * from the actual DOM tree.
   */
  Node previousNode(Node node) {

    Node result;

    // if we're at the root, return null.
    if (node == fRoot) {
      return null;
    }

    // get sibling
    result = node.getPreviousSibling();
    if (result == null) {
      //if 1st sibling, return parent
      result = node.getParentNode();
      return result;
    }

    // if sibling has children, keep getting last child of child.
    if (result.hasChildNodes()
        && !(!fEntityReferenceExpansion
        && result != null
        && result.getNodeType() == Node.ENTITY_REFERENCE_NODE))

    {
      while (result.hasChildNodes()) {
        result = result.getLastChild();
      }
    }

    return result;
  }

  /**
   * Fix-up the iterator on a remove. Called by DOM or otherwise,
   * before an actual DOM remove.
   */
  public void removeNode(Node node) {

    // Implementation note: Fix-up means setting the current node properly
    // after a remove.

    if (node == null) {
      return;
    }

    Node deleted = matchNodeOrParent(node);

    if (deleted == null) {
      return;
    }

    if (fForward) {
      fCurrentNode = previousNode(deleted);
    } else
    // if (!fForward)
    {
      Node next = nextNode(deleted, false);
      if (next != null) {
        // normal case: there _are_ nodes following this in the iterator.
        fCurrentNode = next;
      } else {
        // the last node in the iterator is to be removed,
        // so we set the current node to be the previous one.
        fCurrentNode = previousNode(deleted);
        fForward = true;
      }

    }

  }

  public void detach() {
    fDetach = true;
    fDocument.removeNodeIterator(this);
  }

}
